aliaset
Version:
twind monorepo
475 lines (377 loc) • 13.8 kB
text/typescript
import { error, invalid } from '@sveltejs/kit'
import { normalizeImportMap } from '@jsenv/importmap'
import { Semver } from 'sver'
import { dev } from '$app/environment'
import { env } from '$env/dynamic/private'
import { loadDefaultTemplate, loadTemplate } from '$lib/templates'
import { toBase64, toBase64url } from '$lib/base64'
import type { Manifest, Workspace } from '$lib/types'
import pkg from 'lz-string'
const { decompressFromEncodedURIComponent, compressToEncodedURIComponent } = pkg
import { version as SITE_VERSION } from '../../../package.json'
const MANIFEST_PATH = '/_app/cdn.json'
const HOSTNAME = 'twind-run.pages.dev'
// interface PageData {
// manifests: {
// latest: Manifest | undefined
// next: Manifest | undefined
// canary: Manifest | undefined
// }
// }
import wordlistraw from './wordlist.txt?raw'
// Based on
// - https://gist.github.com/fogleman/c4a1f69f34c7e8a00da8
// - https://www.eff.org/files/2016/09/08/eff_short_wordlist_1.txt from https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases
// 4547 words that are 3-5 chars and good candidates for a prefix search
const wordlist = wordlistraw.trim().split(/\s+/g)
// content adressable store
// twind.run/<base64url(integrity)> -> twind.run/Ttj8VsbHHK9N0k1KS94JH_RYFxFwOQ6D7hFg3XUnSTw
// actions: share
// after login named/aliased urls with default to three named path generated from integrity
// actions: save (same scope), fork (different scope), share
// properties: title, description, slug
// twind.run/~<user>/<alias> -> twind.run/~sastan/brother-santa-bruno
// twind.run/@<team>/<alias> -> twind.run/@twind/brother-santa-bruno
// Metadata of up to 1024 bytes per key
const EXPECTED_VERSION = '1'
import { z } from 'zod'
const workspaceFileSchema = z.object({
path: z.string().min(1),
value: z.string(),
})
const workspaceSchema = z.object({
version: z.string().min(1),
html: workspaceFileSchema,
script: workspaceFileSchema,
config: workspaceFileSchema,
})
export async function load({
request,
params: { key },
platform: { env, caches },
fetch,
}: Parameters<import('./$types').PageServerLoad>[0]) {
const { workspace } = await loadWorkspace()
// default no version: use version from local manifest
// specific version: load manifest from v1-0-0.twind-run.pages.dev
// dist tag: load manifest from twind-run.pages.dev for 'latest' or dist-tag.twind-run.pages.dev
const [localManifest, workspaceManifest, latestManifest, nextManifest] = await Promise.all([
loadManifest(SITE_VERSION),
loadManifest(workspace?.version).catch(() => null),
loadManifest('latest').catch(() => null),
loadManifest('next').catch(() => null),
])
workspace.version = workspaceManifest?.version || ''
if (
!(
workspace.version &&
[
localManifest.version,
workspaceManifest?.version,
latestManifest?.version,
nextManifest?.version,
].includes(workspace.version)
)
) {
workspace.version ||= localManifest.version
}
const manifests = [localManifest, workspaceManifest, latestManifest, nextManifest]
// remove nullish (not found)
.filter(<T>(x: T): x is NonNullable<T> => x != null)
// remove duplicate versions
.filter(
({ version }, index, source) =>
source.findIndex((other) => other.version === version) === index,
)
// sort in reverse order: latest, next, canary
// order: latest (current release), next (upcoming release), canary (PR preview),
.sort((a, b) => Semver.compare(b.version, a.version))
// console.debug({ workspace, manifests })
return { workspace, manifests }
async function loadWorkspace(): Promise<{ workspace: Workspace }> {
if (!key) {
return loadDefaultTemplate()
}
const template = await loadTemplate(key)
if (template) {
return template
}
const data = await env.WORKSPACES.get(key).catch((error) => {
if (error.message.includes('(10020)')) {
// get: The specified object name is not valid. (10020)
// maybe a lz-string encoded url
return null
}
if (error.message.includes('(414)')) {
// get: UTF-8 encoded length of 1747 exceeds key length limit of 1024. (414)
// maybe a lz-string encoded url
return null
}
throw error
})
if (!data) {
const data = decompressFromEncodedURIComponent(key)
if (!data) {
throw error(404, 'Not found')
}
if (!data.startsWith(`${EXPECTED_VERSION}:`)) {
// TODO: better error message
throw error(404, 'Not found')
}
const workspace = JSON.parse(data.slice(`${EXPECTED_VERSION}:`.length))
const result = workspaceSchema.safeParse(workspace)
if (!result.success) {
throw error(400, 'invalid workspace')
}
return { workspace: result.data }
}
if (data.httpMetadata?.contentType !== 'application/json') {
// TODO: better error message
throw error(404, 'Not found')
}
if (data.customMetadata?.version !== EXPECTED_VERSION) {
throw error(404, 'Not found')
}
if (data.httpMetadata.contentEncoding) {
if (data.httpMetadata.contentEncoding !== 'gzip') {
// TODO: better error message
throw error(404, 'Not found')
}
const blob = await toBlob(data.body.pipeThrough(await createGunzipStream()), {
type: 'application/json',
})
return { workspace: JSON.parse(await blob.text()) }
}
return { workspace: await data.json() }
}
async function loadManifest(version: string): Promise<Manifest> {
// Branch name aliases are lowercased and non-alphanumeric characters are replaced with a hyphen
const alias = version === '*' ? 'latest' : version.toLowerCase().replace(/[^a-z\d]/g, '-')
const origin =
version === SITE_VERSION
? new URL(request.url).origin
: alias === 'latest'
? `https://${HOSTNAME}`
: `https://${/^\d\.\d\.\d/.test(alias) ? 'v' + alias : alias}.${HOSTNAME}`
const url = origin + MANIFEST_PATH
const cache = caches?.default
let response = await cache?.match(url)
if (!response) {
console.debug(`Fetching CDN manifest for ${alias || '<empty>'} (${origin})`)
response = await fetch(url)
if (!(response.ok && response.status === 200)) {
throw new Error(`[${response.status}] ${response.statusText || 'request failed'}`)
}
cache?.put(url, response.clone())
}
const manifest = await response.json<Manifest>()
if (manifest.version !== version) {
try {
console.debug(
`Resolving CDN manifest for ${alias || '<empty>'} (${origin}) using ${manifest.version}`,
)
return await loadManifest(manifest.version)
} catch {
console.warn(`Failed to fetch CDN manifest for ${manifest.version}`)
}
}
console.debug(`Loaded CDN manifest for ${alias || '<empty>'} (${origin})`)
return {
...manifest,
...normalizeImportMap(manifest, response.url),
url: response.url,
}
}
}
export const actions: import('./$types').Actions = {
share: async ({ request, platform }) => {
try {
const body = await request.formData()
const token = body.get('cf-turnstile-response')
if (!token) {
return invalid(400, { missing: 'turnstile' })
}
const ip = request.headers.get('CF-Connecting-IP')
// Validate the token by calling the
// "/siteverify" API endpoint.
const trunstileData = new FormData()
trunstileData.append('secret', env.TURNSTILE_SECRET)
trunstileData.append('response', token)
if (ip) {
trunstileData.append('remoteip', ip)
}
const turnstileResult = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
method: 'POST',
body: trunstileData,
},
)
const outcome: {
success: boolean
challenge_ts: string
hostname: string
'error-codes': string[]
action?: string
cdata?: string
} = await turnstileResult.json()
if (!(outcome.success && (dev || outcome.action === 'share'))) {
return invalid(400, outcome)
}
// TODO: ensure the request is valid
// TODO: ensure max request size is 512kb???
const version = body.get('version')
if (version !== EXPECTED_VERSION) {
return invalid(400, { version: 'mismatch' })
}
const workspaceRaw = body.get('workspace')
if (!workspaceRaw) {
return invalid(400, { workspace: 'missing' })
}
if (typeof workspaceRaw !== 'string') {
return invalid(400, { workspace: 'invalid' })
}
const result = workspaceSchema.safeParse(JSON.parse(workspaceRaw))
if (!result.success) {
return invalid(400, { workspace: 'invalid', error: result.error.format() })
}
const blob = new Blob([JSON.stringify(result.data)], { type: 'application/json' })
try {
const { key, integrity, missing } = await generate(platform, await blob.arrayBuffer())
// not found
if (missing) {
// This dance is the only way I could get it work
const value = await toBlob(blob.stream().pipeThrough(await createGzipStream()))
const { readable, writable } =
typeof FixedLengthStream === 'function'
? new FixedLengthStream(value.size)
: { readable: value, writable: null }
await Promise.all([
writable && value.stream().pipeTo(writable),
platform.env.WORKSPACES.put(key, readable, {
customMetadata: { version, integrity },
httpMetadata: {
contentType: 'application/json',
contentEncoding: 'gzip',
},
}),
])
}
return { success: true, key, inserted: missing }
} catch (error) {
return {
success: true,
key: compressToEncodedURIComponent(version + ':' + (await blob.text())),
message: (error as Error).message,
code: (error as any).code,
stack: (error as Error).stack,
}
}
} catch (error) {
return invalid(500, {
message: (error as Error).message,
code: (error as any).code,
stack: (error as Error).stack,
})
}
},
}
async function generate(platform: App.Platform, source: BufferSource) {
const sha256 = await crypto.subtle.digest('SHA-512', source)
const integrity = toBase64(sha256)
const view = new DataView(sha256)
// try different slugs
for (let i = 0; i < view.byteLength - 6; i += 2) {
const key = [
// 2685 * 2685 * 2685 = 19.356.769.125
// <word>-<word>-<word>
wordlist[view.getUint16(i) % wordlist.length],
wordlist[view.getUint16((i += 2)) % wordlist.length],
wordlist[view.getUint16((i += 2)) % wordlist.length],
].join('-')
const existing = await platform.env.WORKSPACES.head(key)
// does not exist or it is the same data
if (
!existing ||
(existing.customMetadata?.integrity && existing.customMetadata.integrity === integrity)
) {
return { key, integrity, missing: !existing }
}
}
// fallback to short readable integrity
{
const key = integrity
.toLowerCase()
// no vowels or similiar looking chars
.replace(/[=+/01aefijlout]/g, '')
.slice(-12)
.replace(/(.{4})(?!$)/g, '$1-')
const existing = await platform.env.WORKSPACES.head(key)
// does not exist or it is the same data
if (
!existing ||
(existing.customMetadata?.integrity && existing.customMetadata.integrity === integrity)
) {
return { key, integrity, missing: !existing }
}
}
// fallback to long readable integrity
{
const key = integrity
.toLowerCase()
// no vowels or similiar looking chars
.replace(/[=+/01aefijlout]/g, '')
.slice(-25)
.replace(/(.{5})(?!$)/g, '$1-')
const existing = await platform.env.WORKSPACES.head(key)
// does not exist or it is the same data
if (
!existing ||
(existing.customMetadata?.integrity && existing.customMetadata.integrity === integrity)
) {
return { key, integrity, missing: !existing }
}
}
// fallback to full integrity
const key = toBase64url(integrity)
const existing = await platform.env.WORKSPACES.head(key)
return { key, integrity, missing: !existing }
}
async function createGzipStream(): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
if (dev && typeof CompressionStream !== 'function') {
const zlib = await import('node:zlib')
const gzip = zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION })
const stream = await import('node:stream')
return {
writable: stream.Writable.toWeb(gzip),
readable: stream.Readable.toWeb(gzip),
}
}
return new CompressionStream('gzip')
}
async function createGunzipStream(): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
if (dev && typeof CompressionStream !== 'function') {
const zlib = await import('node:zlib')
const gzip = zlib.createGunzip()
const stream = await import('node:stream')
return {
writable: stream.Writable.toWeb(gzip),
readable: stream.Readable.toWeb(gzip),
}
}
return new DecompressionStream('gzip')
}
async function toBlob(
readable: ReadableStream<BlobPart>,
options?: BlobPropertyBag,
): Promise<Blob> {
const parts: BlobPart[] = []
await readable.pipeTo(
new WritableStream({
write(chunk) {
parts.push(chunk)
},
}),
)
return new Blob(parts, options)
}